package com.github.dongglin.smart.spring.boot.plus.generator.handler;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.github.dongglin.smart.spring.boot.plus.generator.config.GeneratorConfig;
import com.github.dongglin.smart.spring.boot.plus.generator.enums.DefaultOrderType;
import com.github.dongglin.smart.spring.boot.plus.generator.enums.RequestMappingType;
import com.google.common.base.CaseFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.security.acl.Permission;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Spring Boot Plus 代码生成处理器
 *
 * @author DongGL
 * @date 2023-06-01
 */
@Slf4j
@Data
@Accessors(chain = true)
@SuppressWarnings({"DuplicatedCode", "unused"})
public class GenerateHandler {

    /**
     * 生成代码
     *
     * @param config config
     */
    public void generator(GeneratorConfig config) {
        // 文件是否覆盖
        boolean fileOverride = config.isFileOverride();
        if (!fileOverride) {
            throw new RuntimeException("文件覆盖为false，将不会生成文件");
        }
        // 初始化
        config.init();
        // 循环生成
        Set<String> tableNames = config.getTableNames();
        for (String tableName : tableNames) {
            // 生成代码
            handle(config, tableName);
            log.info(tableName + " 代码生成成功");
        }
    }

    /**
     * @param config    config
     * @param tableName tableName
     */
    public void handle(GeneratorConfig config, String tableName) {
        DataSourceConfig.Builder dataSourceConfigBuilder = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(), config.getPassword()).schema(config.getSchema());
        DbType dbType = dataSourceConfigBuilder.build().getDbType();
        // 当前项目目录
        String projectPath = System.getProperty("user.dir");
        // 如果是仅仅覆盖实体类，则只更新实体类
        boolean onlyOverrideEntity = config.isOnlyOverrideEntity();
        // 代码生成
        FastAutoGenerator.create(dataSourceConfigBuilder).globalConfig(builder -> {
            builder.author(config.getAuthor())// 设置作者
                    .enableSwagger()// 开启 swagger 模式 默认值:false
                    .disableOpenDir()// 禁止打开输出目录 默认值:true
                    .commentDate("yyyy-MM-dd") // 注释日期
                    .dateType(DateType.TIME_PACK)////定义生成的实体类中日期类型 DateType.ONLY_DATE
                    .outputDir(projectPath + "/src/main/java"); // 指定输出目录
        }).packageConfig(builder -> {
            builder.parent(config.getParentPackage())// 父包模块名
                    .moduleName(config.getModuleName())// 设置父包模块名 默认值:无
                    .controller(config.getControllerPackage())//Controller 包名 默认值:controller
                    // 设置mapperXml生成路径
                    .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mapper" + "/" + config.getModuleName()));
        }).strategyConfig(builder -> {
            builder.enableSchema()
                    .addInclude(tableName)// 设置需要生成的表名 可边长参数“user”, “user1”
                    .addTablePrefix(config.getTablePrefix())// 设置过滤表前缀
                    .entityBuilder()// 实体类策略配置
                    .enableFileOverride()
                    .enableLombok()//开启lombok
                    .logicDeleteColumnName("delete_flag")// 说明逻辑删除是哪个字段
                    .idType(config.getIdType())//主键策略  雪花算法自动生成的id
                    .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("update_time", FieldFill.INSERT_UPDATE), new Column("guid", FieldFill.INSERT_UPDATE)) // 自动填充配置
                    .enableTableFieldAnnotation()// 属性加上注解说明
                    .formatFileName(config.getFormatEntityFileName())
                    .controllerBuilder()// controller实体类策略配置
                    .enableFileOverride()
                    .enableRestStyle()// 开启RestController注解
                    .formatFileName(config.getFormatControllerFileName())
                    .serviceBuilder()// service类策略配置
                    .enableFileOverride()
                    .superServiceClass(config.getSuperService())
                    .superServiceImplClass(config.getSuperServiceImpl())
                    .formatServiceFileName(config.getFormatServiceFileName())
                    .formatServiceImplFileName(config.getFormatServiceImplFileName())
                    .mapperBuilder().enableFileOverride() //mapper策略配置
                    .superClass(config.getSuperMapperClass())
                    .formatMapperFileName(config.getFormatMapperFileName())
                    .formatXmlFileName(config.getFormatXmlFileName());

        }).templateConfig(builder -> {
            if (!config.isGeneratorEntity()) {
                builder.entity("");
            }
            // 是否生成controller
            if (!config.isGeneratorController()) {
                builder.controller("");
            }
            // 是否生成service
            if (!config.isGeneratorService()) {
                builder.service("");
            }
            // 是否生成serviceImpl
            if (!config.isGeneratorServiceImpl()) {
                builder.serviceImpl("");
            }
            // 是否生成mapper
            if (!config.isGeneratorMapper()) {
                builder.mapper("");
            }
            if (!config.isGeneratorMapperXml()) {
                builder.xml("");
            }
        }).injectionConfig(builder -> {
            builder.beforeOutputFile(((tableInfo, objectMap) -> {
                addCustomConfigMap(config, tableInfo, objectMap, dbType);
            }));
            String projectPackagePath = config.getProjectPackagePath();
            String moduleName = config.getModuleName();
            String dtoPackage = projectPackagePath + "/" + moduleName + "/" + config.getDtoPackage();
            String queryPackage = projectPackagePath + "/" + moduleName + "/" + config.getQueryPackage();
            String voPackage = projectPackagePath + "/" + moduleName + "/" + config.getVoPackage();
            // 如果是仅仅覆盖实体类，则不更新dto、query、vo
            if (!onlyOverrideEntity) {
                // 自定义addDto
                CustomFile addDtoCustomFile = new CustomFile.Builder().fileName(config.getAddDtoFileName() + ".java").filePath(projectPath + "/src/main/java/").templatePath("/templates/addDto.java.vm").packageName(dtoPackage).enableFileOverride().build();
                // 自定义updateDto
                CustomFile updateDtoCustomFile = new CustomFile.Builder().fileName(config.getUpdateDtoFileName() + ".java").filePath(projectPath + "/src/main/java/").templatePath("/templates/updateDto.java.vm").packageName(dtoPackage).enableFileOverride().build();
                // 自定义query
                CustomFile queryCustomFile = new CustomFile.Builder().fileName(config.getQueryFileName() + ".java").filePath(projectPath + "/src/main/java/").templatePath("/templates/query.java.vm").packageName(queryPackage).enableFileOverride().build();
                // 自定义vo
                CustomFile voCustomFile = new CustomFile.Builder().fileName(config.getVoFileName() + ".java").filePath(projectPath + "/src/main/java/").templatePath("/templates/vo.java.vm").packageName(voPackage).enableFileOverride().build();
                // 自定义infoVo
                CustomFile infoVoCustomFile = new CustomFile.Builder().fileName(config.getInfoVoFileName() + ".java").filePath(projectPath + "/src/main/java/").templatePath("/templates/infoVo.java.vm").packageName(voPackage).enableFileOverride().build();
                // 添加自定义文件
                builder.customMap(Collections.singletonMap("projectName", "spring-boot-plus")).customFile(addDtoCustomFile).customFile(updateDtoCustomFile).customFile(queryCustomFile).customFile(voCustomFile).customFile(infoVoCustomFile);
            }
        }).execute();
    }

    /**
     * 添加自定义配置map
     *
     * @param config    config
     * @param tableInfo tableInfo
     * @param objectMap objectMap
     * @param dbType    dbType
     */
    public void addCustomConfigMap(GeneratorConfig config, TableInfo tableInfo, Map<String, Object> objectMap, DbType dbType) {
        try {
            Map<String, Object> cfgMap = new HashMap<>();
            String tableName = tableInfo.getName();
            String colonTableName = underlineToColon(tableName);
            String entityName = tableInfo.getEntityName();
            String entityObjectName = pascalToCamel(entityName);

            String serviceName = tableInfo.getServiceName();
            String mapperName = tableInfo.getMapperName();
            String serviceObjectName = pascalToCamel(serviceName);
            String mapperObjectName = pascalToCamel(mapperName);

            // 默认的分页排序类型
            DefaultOrderType defaultOrderType = config.getDefaultOrderType();
            // 创建时间列名称
            final String createTimeColumn = config.getCreateTimeColumn();
            // 修改时间列名称
            final String updateTimeColumn = config.getUpdateTimeColumn();
            // 是否生成权限注解
            final boolean permission = config.isPermission();
            // 是否生成简单模式
            final boolean simple = config.isSimple();

            List<TableField> tableFields = tableInfo.getFields();
            String pkIdColumnName = null;
            boolean existsCreateTimeColumn = false;
            boolean existsUpdateTimeColumn = false;
            String updateTimeColumnName = null;
            boolean existsDateType = false;
            boolean existsBigDecimalType = false;
            for (TableField tableField : tableFields) {
                String columnName = tableField.getColumnName();
                String propertyName = tableField.getPropertyName();
                boolean isKeyFlag = tableField.isKeyFlag();
                if (isKeyFlag) {
                    pkIdColumnName = handlePkId(cfgMap, columnName, propertyName);
                } else {
                    if (StringUtils.isNotBlank(createTimeColumn) && createTimeColumn.equalsIgnoreCase(columnName)) {
                        existsCreateTimeColumn = true;
                    } else if (StringUtils.isNotBlank(updateTimeColumn) && updateTimeColumn.equalsIgnoreCase(columnName)) {
                        existsUpdateTimeColumn = true;
                        updateTimeColumnName = columnName;
                    }
                }
                String propertyType = tableField.getPropertyType();
                if (propertyType.equals("Date")) {
                    existsDateType = true;
                } else if (propertyType.equals("BigDecimal")) {
                    existsBigDecimalType = true;
                }
            }

            if (StringUtils.isBlank(pkIdColumnName)) {
                pkIdColumnName = handleDefaultPkId(cfgMap);
            }

            String defaultOrderColumnName = null;
            if (DefaultOrderType.PK_ID == defaultOrderType) {
                defaultOrderColumnName = pkIdColumnName;
            } else if (DefaultOrderType.CREATE_TIME == defaultOrderType) {
                if (existsCreateTimeColumn) {
                    defaultOrderColumnName = createTimeColumn;
                }
            }
            cfgMap.put("defaultOrderType", defaultOrderType);
            cfgMap.put("defaultOrderColumnName", defaultOrderColumnName);

            // 修改列名称
            if (existsUpdateTimeColumn) {
                String pascalUpdateTimeColumnName = underlineToPascal(updateTimeColumnName);
                cfgMap.put("pascalUpdateTimeColumnName", pascalUpdateTimeColumnName);
            }

            // 是否存在日期类型
            cfgMap.put("existsDateType", existsDateType);
            // 是否存在BigDecimal
            cfgMap.put("existsBigDecimalType", existsBigDecimalType);

            // 文件名称格式化
            String addDtoFileName = entityName + config.getAddDtoFileName();
            String updateDtoFileName = entityName + config.getUpdateDtoFileName();
            String queryFileName = entityName + config.getQueryFileName();
            String infoVoFileName = entityName + config.getInfoVoFileName();
            String voFileName = entityName + config.getVoFileName();

            String addDtoObjectName = pascalToCamel(addDtoFileName);
            String updateDtoObjectName = pascalToCamel(updateDtoFileName);
            String queryObjectName = pascalToCamel(queryFileName);
            String infoVoObjectName = pascalToCamel(infoVoFileName);
            String voObjectName = pascalToCamel(voFileName);


            // 包路径
            // dto
            String dtoPackagePath = config.getDtoPackagePath();
            cfgMap.put("dtoPackagePath", dtoPackagePath);
            cfgMap.put("addDtoPath", dtoPackagePath + "." + addDtoFileName);
            cfgMap.put("updateDtoPath", dtoPackagePath + "." + updateDtoFileName);
            // vo
            String voPackagePath = config.getVoPackagePath();
            cfgMap.put("voPackagePath", voPackagePath);
            cfgMap.put("voPath", voPackagePath + "." + voFileName);
            cfgMap.put("infoVoPath", voPackagePath + "." + infoVoFileName);
            // query
            String queryPackagePath = config.getQueryPackagePath();
            cfgMap.put("queryPackagePath", queryPackagePath);
            cfgMap.put("queryPath", queryPackagePath + "." + queryFileName);
            // 导入排序查询参数类
            cfgMap.put("superQueryPath", config.getSuperQuery());
            cfgMap.put("commonFields", config.getCommonFields());
            cfgMap.put("voExcludeFields", config.getVoExcludeFields());
            // 冒号连接的表名称
            cfgMap.put("colonTableName", colonTableName);
            // 是否生成permission注解
            cfgMap.put("permission", permission);
            cfgMap.put("permissionPath", Permission.class.getName());
            cfgMap.put("simple", simple);

            // 导入分页类
            cfgMap.put("paging", config.getCommonPaging());
            // ApiResult
            cfgMap.put("apiResult", config.getCommonApiResult());
            // 分页列表查询是否排序
            cfgMap.put("pageListOrder", config.isPageListOrder());
            cfgMap.put("businessException", config.getCommonBusinessException());
            cfgMap.put("orderByItem", config.getCommonOrderByItem());

            // 代码Validation校验
            cfgMap.put("paramValidation", config.isParamValidation());

            // 对象名称
            cfgMap.put("entityObjectName", entityObjectName);
            // service对象名称
            cfgMap.put("serviceObjectName", serviceObjectName);
            // mapper对象名称
            cfgMap.put("mapperObjectName", mapperObjectName);
            // 自定义文件和对象名称
            cfgMap.put("addDtoName", addDtoFileName);
            cfgMap.put("updateDtoName", updateDtoFileName);
            cfgMap.put("queryName", queryFileName);
            cfgMap.put("infoVoName", infoVoFileName);
            cfgMap.put("voName", voFileName);
            cfgMap.put("addDtoObjectName", addDtoObjectName);
            cfgMap.put("updateDtoObjectName", updateDtoObjectName);
            cfgMap.put("queryObjectName", queryObjectName);
            cfgMap.put("infoVoObjectName", infoVoObjectName);
            cfgMap.put("voObjectName", voObjectName);

            // 请求路径
//            String controllerRequestMappingName = "";
//            String requestMappingName = "";
            // 不通请求映射类型路径设置
            RequestMappingType requestMappingType = config.getRequestMappingType();
//            if (RequestMappingType.CAMEL == requestMappingType) {
//                controllerRequestMappingName = entityObjectName;
//                requestMappingName = entityName;
//            } else if (RequestMappingType.HYPHEN == requestMappingType) {
//                controllerRequestMappingName = toHyphen(entityName);
//                requestMappingName = toHyphen(entityName);
//            } else if (RequestMappingType.UNDERLINE == requestMappingType) {
//                controllerRequestMappingName = toUnderline(entityName);
//                requestMappingName = toUnderline(entityName);
//            } else if (RequestMappingType.BACKSLASH == requestMappingType) {
//                controllerRequestMappingName = toBackslash(entityName);
//                requestMappingName = toBackslash(entityName);
//            }
            // 格式化请求路径
            String controllerRequestMapping = String.format(config.getControllerRequestMapping(), entityObjectName);
            String addRequestMapping = String.format(config.getAddRequestMapping(), entityName);
            String updateRequestMapping = String.format(config.getUpdateRequestMapping(), entityName);
            String deleteRequestMapping = String.format(config.getDeleteRequestMapping(), entityName);
            String infoRequestMapping = String.format(config.getInfoRequestMapping(), entityName);
            String pageRequestMapping = String.format(config.getPageRequestMapping(), entityName);
            // 判断是否需要加上模块名称
            String moduleName = config.getModuleName();
            boolean requestMappingModule = config.isRequestMappingModule();
            if (requestMappingModule) {
                controllerRequestMapping = moduleName + "/" + controllerRequestMapping;
            }
            if (RequestMappingType.HYPHEN == requestMappingType) {
                controllerRequestMapping = toHyphen(controllerRequestMapping);
                addRequestMapping = toHyphen(addRequestMapping);
                updateRequestMapping = toHyphen(updateRequestMapping);
                deleteRequestMapping = toHyphen(deleteRequestMapping);
                infoRequestMapping = toHyphen(infoRequestMapping);
                pageRequestMapping = toHyphen(pageRequestMapping);
            } else if (RequestMappingType.UNDERLINE == requestMappingType) {
                controllerRequestMapping = toUnderline(controllerRequestMapping);
                addRequestMapping = toUnderline(addRequestMapping);
                updateRequestMapping = toUnderline(updateRequestMapping);
                deleteRequestMapping = toUnderline(deleteRequestMapping);
                infoRequestMapping = toUnderline(infoRequestMapping);
                pageRequestMapping = toUnderline(pageRequestMapping);
            } else if (RequestMappingType.BACKSLASH == requestMappingType) {
                controllerRequestMapping = toBackslash(controllerRequestMapping);
                addRequestMapping = toBackslash(addRequestMapping);
                updateRequestMapping = toBackslash(updateRequestMapping);
                deleteRequestMapping = toBackslash(deleteRequestMapping);
                infoRequestMapping = toBackslash(infoRequestMapping);
                pageRequestMapping = toBackslash(pageRequestMapping);
            } else if (RequestMappingType.LOWER == requestMappingType) {
                controllerRequestMapping = toLower(controllerRequestMapping);
                addRequestMapping = toLower(addRequestMapping);
                updateRequestMapping = toLower(updateRequestMapping);
                deleteRequestMapping = toLower(deleteRequestMapping);
                infoRequestMapping = toLower(infoRequestMapping);
                pageRequestMapping = toLower(pageRequestMapping);
            }

            cfgMap.put("controllerRequestMapping", controllerRequestMapping);
            cfgMap.put("addRequestMapping", addRequestMapping);
            cfgMap.put("updateRequestMapping", updateRequestMapping);
            cfgMap.put("deleteRequestMapping", deleteRequestMapping);
            cfgMap.put("infoRequestMapping", infoRequestMapping);
            cfgMap.put("pageRequestMapping", pageRequestMapping);

            objectMap.put("cfg", cfgMap);
        } catch (Exception e) {
            log.error("" + e);
            System.exit(0);
        }
    }

    /**
     * @param cfgMap cfgMap
     * @return String
     */
    private String handleDefaultPkId(Map<String, Object> cfgMap) {
        return handlePkId(cfgMap, "id", "id");
    }

    /**
     * @param cfgMap       cfgMap
     * @param columnName   columnName
     * @param propertyName propertyName
     * @return String
     */
    private String handlePkId(Map<String, Object> cfgMap, String columnName, String propertyName) {
        String pkIdColumnName;
        pkIdColumnName = columnName;
        cfgMap.put("pkIdColumnName", pkIdColumnName);
        if (StringUtils.isNotBlank(propertyName)) {
            String pascalPkIdName = camelToPascal(propertyName);
            cfgMap.put("pascalPkIdName", pascalPkIdName);
            cfgMap.put("pkIdName", propertyName);
        }
        return pkIdColumnName;
    }


    /**
     * 下划线专程驼峰命名
     * sys_user --> sysUser
     *
     * @param underline underline
     * @return String
     */
    public static String underlineToCamel(String underline) {
        if (StringUtils.isNotBlank(underline)) {
            return NamingStrategy.underlineToCamel(underline);
        }
        return null;
    }

    /**
     * 下划线转换成帕斯卡命名
     * sys_user --> SysUser
     *
     * @param underline underline
     * @return String
     */
    public static String underlineToPascal(String underline) {
        if (StringUtils.isNotBlank(underline)) {
            return NamingStrategy.capitalFirst(NamingStrategy.underlineToCamel(underline));
        }
        return null;
    }

    /**
     * 骆驼转换成帕斯卡命名
     * roleId --> RoleId
     *
     * @param camel camel
     * @return string
     */
    public static String camelToPascal(String camel) {
        if (StringUtils.isNotBlank(camel)) {
            return NamingStrategy.capitalFirst(camel);
        }
        return null;
    }

    /**
     * 下划线转换成冒号连接命名
     * sys_user --> sys:user
     *
     * @param underline underline
     * @return string
     */
    public static String underlineToColon(String underline) {
        if (StringUtils.isNotBlank(underline)) {
            String string = underline.toLowerCase();
            return string.replaceAll("_", ":");
        }
        return null;
    }

    /**
     * 帕斯卡转换成驼峰命名法
     * SysUser --> sysUser
     *
     * @param pascal pascal
     * @return String
     */
    public static String pascalToCamel(String pascal) {
        if (StringUtils.isNotBlank(pascal)) {
            return pascal.substring(0, 1).toLowerCase() + pascal.substring(1);
        }
        return pascal;
    }

    /**
     * 下划线转换成横线连接命名
     * sys_user --> sys-user
     *
     * @param underline underline
     * @return String
     */
    public static String underlineToHyphen(String underline) {
        if (StringUtils.isNotBlank(underline)) {
            String string = underline.toLowerCase();
            return string.replaceAll("_", "-");
        }
        return null;
    }

    /**
     * 转换成中横线连接
     * sys-user
     *
     * @param value value
     * @return String
     */
    public static String toHyphen(String value) {
        if (StringUtils.isNotBlank(value)) {
            String result = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, value);
            return result.replaceAll("_", "-");
        }
        return null;
    }

    /**
     * 转换成下划线
     *
     * @param value value
     * @return String
     */
    public static String toUnderline(String value) {
        if (StringUtils.isNotBlank(value)) {
            return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, value);
        }
        return null;
    }

    /**
     * 转换成反斜杠
     *
     * @param value value
     * @return String
     */
    public static String toBackslash(String value) {
        if (StringUtils.isNotBlank(value)) {
            String result = toHyphen(value);
            return Objects.requireNonNull(result).replaceAll("-", "/");
        }
        return null;
    }

    /**
     * 转换成小写
     *
     * @param value value
     * @return String
     */
    public static String toLower(String value) {
        if (StringUtils.isNotBlank(value)) {
            return value.toLowerCase();
        }
        return null;
    }
}
